/*
 * Copyright 2002-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.scope;

import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Utility class for creating a scoped proxy.
 *
 * <p>Used by ScopedProxyBeanDefinitionDecorator and ClassPathBeanDefinitionScanner.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @author Sam Brannen
 * @since 2.5
 */
public abstract class ScopedProxyUtils {

	private static final String TARGET_NAME_PREFIX = "scopedTarget.";

	private static final int TARGET_NAME_PREFIX_LENGTH = TARGET_NAME_PREFIX.length();


	/**
	 * Generate a scoped proxy for the supplied target bean, registering the target
	 * bean with an internal name and setting 'targetBeanName' on the scoped proxy.
	 * <br> 给提供的目标bean创建一个 作用域代理 , 并使用内部名称注册目标bean, 并在作用域代理上设置'targetBeanName'.
	 *
	 * @param definition the original bean definition
	 * @param registry the bean definition registry
	 * @param proxyTargetClass whether to create a target class proxy
	 * @return the scoped proxy definition
	 * @see #getTargetBeanName(String)
	 * @see #getOriginalBeanName(String)
	 */
	public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
			BeanDefinitionRegistry registry, boolean proxyTargetClass) {
		//1、获取原始bean名称
		String originalBeanName = definition.getBeanName();
		//2、获取原始bean定义作为 做域名的目标bean定义
		BeanDefinition targetDefinition = definition.getBeanDefinition();
		//3、根据原始bean名称生成目标bean名称
		String targetBeanName = getTargetBeanName(originalBeanName);

		// Create a scoped proxy definition for the original bean name,
		// "hiding" the target bean in an internal target definition.
		//4、 给原始bean名称创建一个作用域代理定义, 在一个内部目标定义中 "隐藏" 目标bean.
		RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
		//5、proxyDefinition代理定义 设置被装饰的定义, 即原始bean定义holder，holder持有者：beanName = scopedTarget.originalBeanName, beanDefinition = 原始bean定义
		proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
		//6、proxyDefinition代理定义 设置原始bean定义的来源, 即原始bean定义
		proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
		//7、proxyDefinition代理定义 设置原始bean定义的来源，来源是?
		proxyDefinition.setSource(definition.getSource());
		//8、proxyDefinition代理定义设置角色定义，从目标表角色属性复制
		proxyDefinition.setRole(targetDefinition.getRole());

		//9、代理bean定义添加 属性字段 targetBeanName，存储 目标bean名称字段(scopedTarget.originalBeanName)
		proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
		if (proxyTargetClass) {
			//10、目标bean定义设置属性字段org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass，存储true值
			targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
			// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
			// 因为ScopedProxyFactoryBean的"proxyTargetClass"默认是TRUE, 所以我们不需要在这里显式地设置它。
		}
		else {
			proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
		}

		// Copy autowire settings from original bean definition.
		//11、复制原始bean定义的自动装配设置
		proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
		//12、复制原始bean定义的主要设置
		proxyDefinition.setPrimary(targetDefinition.isPrimary());
		if (targetDefinition instanceof AbstractBeanDefinition abd) {
			//13、如果目标bean定义是AbstractBeanDefinition， 复制原始bean定义的限定符
			proxyDefinition.copyQualifiersFrom(abd);
		}

		// The target bean should be ignored in favor of the scoped proxy.
		//14、忽略目标bean的定义中的 自动装配候选人和主要设置
		targetDefinition.setAutowireCandidate(false);
		targetDefinition.setPrimary(false);

		// Register the target bean as separate bean in the factory.
		//15、在工厂中注册目标bean作为单独的bean,beanName = scopedTarget.originalBeanName, beanDefinition = 原始bean定义
		registry.registerBeanDefinition(targetBeanName, targetDefinition);

		// Return the scoped proxy definition as primary bean definition
		// (potentially an inner bean).
		//16、返回作用域代理定义作为主要bean定义(可能是内部bean)
		//代理bean定义持有者：beanName = originalBeanName, beanDefinition = 代理bean定义,别名 = 原始bean定义持有者别名
		return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
	}

	/**
	 * Generate the bean name that is used within the scoped proxy to reference the target bean.
	 * @param originalBeanName the original name of bean
	 * @return the generated bean to be used to reference the target bean
	 * @see #getOriginalBeanName(String)
	 */
	public static String getTargetBeanName(String originalBeanName) {
		return TARGET_NAME_PREFIX + originalBeanName;
	}

	/**
	 * Get the original bean name for the provided {@linkplain #getTargetBeanName
	 * target bean name}.
	 * @param targetBeanName the target bean name for the scoped proxy
	 * @return the original bean name
	 * @throws IllegalArgumentException if the supplied bean name does not refer
	 * to the target of a scoped proxy
	 * @since 5.1.10
	 * @see #getTargetBeanName(String)
	 * @see #isScopedTarget(String)
	 */
	public static String getOriginalBeanName(@Nullable String targetBeanName) {
		Assert.isTrue(isScopedTarget(targetBeanName), () -> "bean name '" +
				targetBeanName + "' does not refer to the target of a scoped proxy");
		return targetBeanName.substring(TARGET_NAME_PREFIX_LENGTH);
	}

	/**
	 * Determine if the {@code beanName} is the name of a bean that references
	 * the target bean within a scoped proxy.
	 * @since 4.1.4
	 */
	public static boolean isScopedTarget(@Nullable String beanName) {
		return (beanName != null && beanName.startsWith(TARGET_NAME_PREFIX));
	}

}
